package functionalEnrichment.semanticSimilarity.semanticSimilarityCalculator.parsers;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import functionalEnrichment.semanticSimilarity.semanticSimilarityCalculator.units.GoTerm;
import functionalEnrichment.semanticSimilarity.semanticSimilarityCalculator.units.Ontology;
import functionalEnrichment.semanticSimilarity.semanticSimilarityCalculator.units.OntologyType;
import functionalEnrichment.semanticSimilarity.semanticSimilarityCalculator.utils.FileUtils;

/**
 * Parses an .obo file. These files contain the relationship of GO terms to one another, as
 * a directed acyclic graph (DAG). .obo files can be downloaded from the GO consortium website.
 * @author Jamie MacPherson
 */
public class OboParser {
	
    // a map with all GO terms, the key being the term accession (minus the non numerical "GO:" prefix)
    GoTermLibrary goTermLibrary = new GoTermLibrary();
	
    /**
     * The public method of the OboParser. Returns an <code>Ontology</code> object that stores the GO ontology data.
     *The program will exit if a root term is not found in the .obo file.
     *
     * @param oboFile the .obo file
     * @param ontologyType the GO ontology, either Biological Process (P), Molecular Function (F) or Cellular Component (C)
     * @return the <code>Ontology</code> object that stores the ontology data.
     */
    public Ontology getOntology(File oboFile, OntologyType ontologyType) {
        GoTerm root = null;
        try {
            root = getRoot(oboFile, ontologyType);
        } catch (IOException ex) {
            System.out.println("exiting progem");
            ex.printStackTrace();
            System.exit(0);
        }
		
        addDescendents(oboFile, ontologyType);
        Ontology ontology = new Ontology();
        ontology.setGoOntology(ontologyType);
        ontology.setRoot(root);
        ontology.setGoTerms(goTermLibrary.getTermHash());
        return ontology;
    }
	
    /**
     * Searches for and returns the root term of the ontology, i.e., the one that has the same name as the ontology itself.
     *
     * @param oboFile the .obo file to find the term.
     * @param ontologyType the GO ontology that is being parsed, i.e., one of
     * Biological Process (P), Molecular Function (F) or Cellular Component (C),
     * represented by one of the <code>OntologyType</code> objects.
     * Will throw an <code>IOexception</code> if no root term is found.
     * @return the root go term.
     */
    private GoTerm getRoot(File oboFile, OntologyType ontologyType) throws IOException {
        String line;
        BufferedReader reader = FileUtils.getBufferedReader(oboFile);
        GoTerm root = null;
	search:
        {
            try {
                int currentId = -1;
                String nameSpace = "no namespace";
                String name = "no name";
                while ((line = reader.readLine()) != null) {
                    if (line.startsWith("id: GO:")) {
                        if (nameSpace.equals(ontologyType.name().toLowerCase()) && name.equals(nameSpace)) {
                            root = goTermLibrary.getGoTerm(currentId);
                            root.setName(name);
                            break search;
                        }
                        currentId = Integer.parseInt(line.split("id: GO:")[1]);
                    } else if (line.startsWith("namespace: ")) {
                        nameSpace = line.split("namespace: ")[1];
                    } else if (line.startsWith("name: ")) {
                        name = line.split("name: ")[1];
                    }
                }
                if (nameSpace.equals(ontologyType.name().toLowerCase()) && name.equals(nameSpace)) {
                    root = new GoTerm(currentId);
                }
                reader.close();
            } catch (IOException ex) {
                Logger.getLogger(OboParser.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
        if (root == null) {
            throw new IOException("no root term was found in the .obo file");
			
        }
        return root;
    }
	
    /**
     * Reads the given .obo file and adds any descendent terms to the currently stored toot term in the <code>goTerms</code> map.
     * The int <code>count</code> counts the number of terms that have been added. If <code>count</code> increases on a pass of the .obo
     * file, the method recursively calls itself until <code>count</code> remains the same, i.e., all decendent terms have been added.
     * @param oboFile the .obo file to be read.
     * @param ontologyType the GO ontology that is being parsed, i.e., one of
     * Biological Process (P), Molecular Function (F) or Cellular Component (C),
     * represented by one of the <code>OntologyType</code> objects.
     * @param count the number of descendent terms that have been added to the DAG
     * @return the count of terms added to the DAG.
     */
    private void addDescendents(File oboFile, OntologyType ontologyType) {
        BufferedReader reader = FileUtils.getBufferedReader(oboFile);
        String line;
		
        try {
            int currentId = -1;
			
            String nameSpace = "no namespace";
            String name = "no name";
            while ((line = reader.readLine()) != null) {
                if (line.startsWith("id: GO:")) {
                    currentId = Integer.parseInt(line.split("id: GO:")[1]);
                } else if (line.startsWith("is_a: GO:") && nameSpace.equals(ontologyType.name().toLowerCase())) {
                    String isa = line.split("is_a: GO:")[1].split(" ! ")[0];
                    int isaInt = Integer.parseInt(isa.trim());
                    GoTerm parent = goTermLibrary.getGoTerm(isaInt);
                    GoTerm gt = goTermLibrary.getGoTerm(currentId);
                    int index = 1;
                    if (parent.lastIndex() != -1) {
                        index = parent.lastIndex() + 1;
                    }
                    parent.embed(gt, index);
                    gt.addParent(parent);
                } else if (line.startsWith("relationship: ") && nameSpace.equals(ontologyType.name().toLowerCase())) {
                    String relationship = line.split(":")[2].split(" ")[0];
                    int isaInt = Integer.parseInt(relationship.trim());
                    GoTerm parent = goTermLibrary.getGoTerm(isaInt);
                    GoTerm gt = goTermLibrary.getGoTerm(currentId);
                    int index = 1;
                    if (parent.lastIndex() != -1) {
                        index = parent.lastIndex() + 1;
                    }
                    parent.embed(gt, index);
                    gt.addParent(parent);
                } else if (line.startsWith("namespace: ")) {
                    nameSpace = line.split("namespace: ")[1];
                } else if (line.startsWith("name: ")) {
                    name = line.split("name: ")[1];
                    GoTerm gt = goTermLibrary.getGoTerm(currentId);
                    gt.setName(name);
                }
            }
            reader.close();
        } catch (IOException ex) {
            Logger.getLogger(OboParser.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
	
    private class GoTermLibrary {
		
        private HashMap<Integer, GoTerm> termHash = new HashMap<Integer, GoTerm>();
		
        public GoTerm getGoTerm(int termId) {
            if (termHash.containsKey(termId)) {
                return termHash.get(termId);
            } else {
                GoTerm gt = new GoTerm(termId);
                termHash.put(termId, gt);
                return gt;
            }
        }
		
        public HashMap<Integer, GoTerm> getTermHash() {
            return termHash;
        }
    }
}
